import json

from django.test import TestCase, Client


class BaseTestApi(TestCase):
    """
    作为测试的基类， 要求所有的测试类必须继承这个类
    1. 正常测试，利用函数 self._get_error_msg() 获取对应的异常信息，此函数返回的是个字符串，可以根据需要拼接，使用换行符，便于查看
    2. 预计错误的测试，要求由 函数名 中包含 'wrong'；而且，需要执行 self._wrong() 方法。
    """
    fixtures = ['users.json']

    def _get_username(self):
        return "10000560092"

    def _get_error_msg(self, data: dict, url: str, method: str = "get"):
        """
        组织异常信息
        """
        msg = '\n' + '接口地址：' + url + '\n' + '接口方法：' + method + '\n' + '参数：' + json.dumps(data,
                                                                                         ensure_ascii=False) + '\n'
        return msg

    def setUp(self):
        self.client = Client(raise_request_exception=False)
        self.address = "http://127.0.0.1/"

    def get_header(self):
        return {'HTTP_USERNAME': self._get_username(), }

    def check_structure(self, response: dict, structure: dict, can_empty: list, can_not_exist: list, base_key: str,
                        base_msg: str):
        """
        校验结构体，值的类型，是否为空
        @param response: 响应
        @param structure: 结构体
        @param can_not_exist: 结构体中可以不包含这个键
        @param can_empty: 值为列表时，可以为空的对应键
        @param base_key: response对应的key
        @param base_msg: 定义异常信息
        @return:
        """
        if not isinstance(structure, dict):
            # 对象不为字典的时候，只校验数据类型是否一致
            if response is not None or structure is not None:
                type_r, type_s = str(type(structure)), str(type(response))
                msg = f'{base_key}:对应的值的类型不正确 ---> :{type_r}......{type_s}'
                self.assertEqual(type_s, type_r, base_msg + msg + '\n')
            return

        for key, value in structure.items():
            if key in can_not_exist and key not in response:
                continue
            self.assertIn(key, response, f'返回结构体中不包含：{key}')
            response_value = response[key]
            if value is not None and response_value is not None:
                type_r, type_s = type(value), type(response_value)
                msg = f'{key}:对应的值的类型不正确 ---> :{str(type_r)}......{str(type_s)}'
                self.assertEqual(type_s, type_r, base_msg + msg + '\n')
            type_v = type(value)

            if type_v is dict:
                self.check_structure(response_value, value, can_empty, can_not_exist, key, base_msg)
                continue
            # value为列表时
            elif type_v is list:
                if response_value is None:
                    # self.assertNotEqual(0, 0, base_msg + f'{key}: 必须有内容' + '\n')
                    raise ValueError(base_msg + f'{key}: 必须有内容' + '\n')
                response_value_len = len(response_value)
                if response_value and value:
                    self.check_structure(response_value[0], value[0], can_empty, can_not_exist, key, base_msg)
                elif key in can_empty:
                    continue
                else:
                    self.assertNotEqual(response_value_len, 0, base_msg + f'{key}: 必须有内容' + '\n')

    def get_response(self, url, data=None, can_empty=None, can_not_exist=None, method="get", **kwargs):
        """
        获取请求
        @param url: 路由
        @param data: 接口参数
        @param method: 请求方法
        @param can_not_exist: 结构体中可以不包含这个键
        @param can_empty: 对象中为列表的数据是可以为空的键，每一级字典的键
        @param kwargs:{'structure_0':{}, 'structure_1':{},'structure_2':{}, is_structure=True, is_wrong=False},
            不同status对应不同的结构体，kwargs[status], is_structure: 是否校验structure, is_wrong: 是否是测试异常
        @return:
        """
        header = self.get_header()
        absolute_url = self.address + url
        data = {} if not isinstance(data, dict) else data
        can_not_exist = [] if not isinstance(can_not_exist, list) else can_not_exist
        if method == "get":
            response = self.client.get(absolute_url, data, **header)
        elif method == 'put':
            response = self.client.put(absolute_url, data, content_type='application/json', **header)
        elif method == "post":
            response = self.client.post(absolute_url, data, content_type='application/json', **header)
        elif method == 'delete':
            response = self.client.delete(absolute_url, data, content_type='application/json', **header)
        else:
            response = self.client.get(absolute_url, data, **header)
        status_code = response.status_code
        self.assertEqual(status_code, 200, '接口未正常返回数据。status_code={}'.format(status_code))
        if status_code != 200:
            self.skipTest('接口未正常返回数据。status_code={}'.format(status_code))
        response = response.json()
        status = response.get('status', 200)
        # 校验状态码是否正确
        if 'status' in kwargs:
            self.assertEqual(status, kwargs['status'])
        if kwargs.get('is_structure', True):
            base_msg = self._get_error_msg(data, absolute_url, method)
            if status in {200, 401} and not kwargs.get('is_wrong', False):
                self.assertEqual(status, 200, msg=base_msg + '\n原因：这个接口返回的状态不为1，请查找原因')
                self.skipTest('')
            structure = kwargs.get('structure_' + str(status), {'msg': "", 'status': 200})
            can_empty = [] if can_empty is None else can_empty
            self.check_structure(response, structure, can_empty, can_not_exist, 'response', base_msg)
        return response

    def wrong(self, url, data, status: list = None, **kwargs):
        """
        测试错误
        :param url: 路径
        :param data: 错误参数，可迭代对象
        :param status: 判断返回结果的状态码
        :return:
        """
        is_structure = kwargs.get('is_structure', False)
        status = [] if status is None else status
        status = [404, 401] + status
        for p in data:
            response = self.get_response(url, data=p, is_structure=is_structure, is_wrong=True, **kwargs)
            base_msg = self._get_error_msg(p, url, kwargs.get('method', "get"))
            self.assertIn(response['status'], status, base_msg + "\n信息：这个接口未按照预期返回不正常信息，请检查，或修改测试")
